package ctxSemanticsGen;


import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.runtime.RuntimeConstants;
import org.eclipse.core.runtime.Platform;

/**
 * Generates the aspect that waves the contextual semantics for a specific 
 * method of a given class.
 * 
 * @author guido
 *
 */
public class AspectGenerator {
	
	
	private ClassStructure layeredClass;
	private Method advicedMethod;
	private Collection<Method> inLayerMethods;
	
	
	public void setClassStructure(ClassStructure c){
		layeredClass = c;
	}
	
	public void setAdvicedMethod(Method m){
		advicedMethod = m;
	}
	
	public void setLayeredMethods(Collection<Method> lm){
		inLayerMethods = lm;
	}

	
	/**
	 * Generates the aspect with given name at the given path.
	 * An exception is thrown if one of the required values is not set.
	 * For example: generateAspect("./out") creates the file ./out/test.aj
	 * @param path
	 * @param name
	 */
	public void generateAspect(String path){
		if (layeredClass == null || advicedMethod == null || inLayerMethods == null){
			throw new RuntimeException("Missing parameter");
		}
		// path + "/"
		run(path + Constants.FILE_SEPARATOR);
	}
	

	
	@SuppressWarnings("deprecation")
	private void run(String path){

        VelocityContext context = new VelocityContext();
        
        String aspectName = Utility.buildUniqueMethodName(advicedMethod);
        context.put("aspectName", aspectName);
        
        context.put("package", layeredClass.getDescribedClass().getPackage().getName());
        
        context.put("className", layeredClass.getDescribedClass().getName());
        
        context.put("advicedMethodName", advicedMethod.getName());
        context.put("advicedMethodSignature", advicedMethod.toGenericString());
        
        context.put("baseLayerWrapperName", 
        		Utility.buildUniqueLayerWrapperName(Utility.BASE_LAYER_NAME));
        
        
        /*
         * An array for each layered method is created.
         * 0 -> method name
         * 1 -> complete signature, return type, class, name, parameter types
         * 2 -> layerWrapper associated to the layer to which the method belong
         * 3 -> generated unique name for the method
         */
        ArrayList<String[]> inLayerMethodStrings = new ArrayList<String[]>();
        for (Method m: inLayerMethods){
        	
        	// Get the layerWrapper name for the layer associated to this method
        	String l = Utility.layerForName(m.getName(),layeredClass.getLayerMapper());
        	String layerWrapper = Utility.buildUniqueLayerWrapperName(l);
        	
        	String uniqueName = Utility.buildUniqueMethodName(m);
        	
        	String[] s = {m.getName(), m.toGenericString(), layerWrapper, uniqueName};
        	inLayerMethodStrings.add(s);
        }
        context.put("inLayerMethodStrings", inLayerMethodStrings);
        
        
   
        /*
         *   The list contains an array for each parameter of the method.
         *   The first element contain the type for the cast, "(String)" in the
         *   example the second element contains the position of the parameter
         *   and is used for getArgs()[0].
         *   
         *   return lw.Person_print(lw, p,(String)thisJoinPoint.getArgs()[0]);
         */
        ArrayList<String[]> advicedMethodParametersType = new ArrayList<String[]>();
        int i = 0; 
        for(Type t: advicedMethod.getGenericParameterTypes()){
        	String[] s = {Utility.sanitizeGenericParameterType(t.toString()),
        				  Utility.sanitizeGenericParameterType(Integer.toString(i))};
        	advicedMethodParametersType.add(s);
        	i++;
        }
        context.put("advicedMethodParametersType", advicedMethodParametersType);
        context.put("advicedMethodUniqueName", Utility.buildUniqueMethodName(advicedMethod));
        
        
        /* Return Value */
		boolean returnsAValue = true;
		if (advicedMethod.getGenericReturnType().toString().equals("void")){
			returnsAValue = false;
		}
		context.put("advicedMethodReturnValue",returnsAValue);
        
        
       
        /* render the template TODO: Add the encoding */
        StringWriter w = new StringWriter();
        Velocity.mergeTemplate("aspectTemplate.vm", context, w );
        
        
        try {
        	
        	/* Create the package if needed */
        	String s = layeredClass.getDescribedClass().getPackage().getName();
        	
        	// s = s.replace('.','/');
        	s = s.replace('.',Constants.FILE_SEPARATOR.charAt(0));
        	s = path + Constants.FILE_SEPARATOR + s + Constants.FILE_SEPARATOR;
        	(new File(s)).mkdirs();
        	
        	/* Generate the file */
        	BufferedWriter out = new BufferedWriter(new FileWriter(s + aspectName + ".aj"));
        	out.write(w.toString());
        	out.close();
        }catch (IOException e){
        	System.out.print("Error writing generated aspect to file!");
        	System.exit(1);
        }
	
	}

}
